﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Media3D;

namespace SharpSoft.WPF.Controls
{
    [Localizability(LocalizationCategory.Ignore)]
    [ContentProperty("Children")]
    public class Gallery3D : Control, System.Windows.Markup.IAddChild
    {
        static Gallery3D()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Gallery3D), new FrameworkPropertyMetadata(typeof(Gallery3D)));
        }
        public Gallery3D()
        {
            this.CameraPosition = new Point3D(0, 0, 11);
            this.CameraLookDirection = new Vector3D(0, 0, -1);
            this.CameraFieldOfView = 50d;
            this.LightDirection = new Vector3D(0, 0, -1);
            this.Children = new System.Collections.ObjectModel.ObservableCollection<UIElement>();
            this.Children.CollectionChanged += Children_CollectionChanged;
            this.PositionProvider = new CornerGallery3DPositionProvider();
        }
        #region 属性

        #region CAMERA

        /// <summary>
        /// 相机位置
        /// </summary>
        public Point3D CameraPosition
        {
            get { return (Point3D)GetValue(CameraPositionProperty); }
            set { SetValue(CameraPositionProperty, value); }
        }

        public static readonly DependencyProperty CameraPositionProperty =
            DependencyProperty.Register("CameraPosition", typeof(Point3D), typeof(Gallery3D), new PropertyMetadata(new Point3D()));
        /// <summary>
        /// 相机的方向
        /// </summary>
        public Vector3D CameraLookDirection
        {
            get { return (Vector3D)GetValue(CameraLookDirectionProperty); }
            set { SetValue(CameraLookDirectionProperty, value); }
        }

        public static readonly DependencyProperty CameraLookDirectionProperty =
            DependencyProperty.Register("CameraLookDirection", typeof(Vector3D), typeof(Gallery3D), new PropertyMetadata(new Vector3D()));
        /// <summary>
        /// 视野宽度
        /// </summary>
        public double CameraFieldOfView
        {
            get { return (double)GetValue(CameraFieldOfViewProperty); }
            set { SetValue(CameraFieldOfViewProperty, value); }
        }

        public static readonly DependencyProperty CameraFieldOfViewProperty =
            DependencyProperty.Register("CameraFieldOfView", typeof(double), typeof(Gallery3D), new PropertyMetadata(0d));
        /// <summary>
        /// 灯光方向
        /// </summary>
        public Vector3D LightDirection
        {
            get { return (Vector3D)GetValue(LightDirectionProperty); }
            set { SetValue(LightDirectionProperty, value); }
        }

        public static readonly DependencyProperty LightDirectionProperty =
            DependencyProperty.Register("LightDirection", typeof(Vector3D), typeof(Gallery3D), new PropertyMetadata(new Vector3D()));

        #endregion

        #region INDEX
        /// <summary>
        /// 当前对象的索引（注意当前对象不一定是突出显示的对象，显示位置还受<see cref="Offset"/>属性的影响。）
        /// </summary>
        public int CurrentIndex
        {
            get { return (int)GetValue(CurrentIndexProperty); }
            set { SetValue(CurrentIndexProperty, value); }
        }

        public static readonly DependencyProperty CurrentIndexProperty =
            DependencyProperty.Register("CurrentIndex", typeof(int), typeof(Gallery3D), new PropertyMetadata(0));
        /// <summary>
        /// 位置提供器
        /// </summary>
        public IGallery3DPositionProvider PositionProvider
        {
            get { return (IGallery3DPositionProvider)GetValue(PositionProviderProperty); }
            set { SetValue(PositionProviderProperty, value); }
        }

        public static readonly DependencyProperty PositionProviderProperty =
            DependencyProperty.Register("PositionProvider", typeof(IGallery3DPositionProvider), typeof(Gallery3D), new PropertyMetadata(default(IGallery3DPositionProvider)));
        /// <summary>
        /// 画轴的偏移(相对于当前索引的偏移，1单位偏移=1单位索引位置)
        /// </summary>
        public double Offset
        {
            get { return (double)GetValue(OffsetProperty); }
            set { SetValue(OffsetProperty, value); }
        }

        public static readonly DependencyProperty OffsetProperty =
            DependencyProperty.Register("Offset", typeof(double), typeof(Gallery3D), new PropertyMetadata(0d));
        /// <summary>
        /// 用于按比例调节用户操作是画轴滚动的速度，默认比例为1。
        /// </summary>
        public double SpeedRatio
        {
            get { return (double)GetValue(SpeedRatioProperty); }
            set { SetValue(SpeedRatioProperty, value); }
        }

        public static readonly DependencyProperty SpeedRatioProperty =
            DependencyProperty.Register("SpeedRatio", typeof(double), typeof(Gallery3D), new PropertyMetadata(1d));
        /// <summary>
        /// 是否允许转到子元素数量范围外的索引处
        /// </summary>
        public bool AllowOutOfRange
        {
            get { return (bool)GetValue(AllowOutOfRangeProperty); }
            set { SetValue(AllowOutOfRangeProperty, value); }
        }

        public static readonly DependencyProperty AllowOutOfRangeProperty =
            DependencyProperty.Register("AllowOutOfRange", typeof(bool), typeof(Gallery3D), new PropertyMetadata(false));


        protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
            base.OnPropertyChanged(e);
            if (e.Property == CurrentIndexProperty || e.Property == OffsetProperty || e.Property == PositionProviderProperty)
            {
                ResetPosition();
                if (e.Property == CurrentIndexProperty)
                {
                    CurrentIndexChanged?.Invoke(this, EventArgs.Empty);
                }
            }
        }

        public event EventHandler CurrentIndexChanged;
        /// <summary>
        /// 动画到当前索引的指定偏移处
        /// </summary>
        /// <param name="offset"></param>
        /// <param name="dur">动画持续时间</param>
        public void AnimationTo(double offset, TimeSpan? dur = null)
        {
            DoubleAnimation da = new DoubleAnimation() { Duration = TimeSpan.FromSeconds(1), AccelerationRatio = 0.5d, DecelerationRatio = 0.5d };
            this.AnimationTo(offset, da);
        }
        /// <summary>
        /// 使用指定的<see cref="DoubleAnimation"/>将视图从当前偏移动画到指定的偏移位置
        /// </summary>
        /// <param name="offset"></param>
        /// <param name="doubleAnimation"></param>
        public void AnimationTo(double offset, DoubleAnimation doubleAnimation)
        {
            //if (anicount > 0)
            //{
            //    return;
            //}
            this.anicount++;
            doubleAnimation.From = null;
            doubleAnimation.To = offset;
            doubleAnimation.FillBehavior = FillBehavior.Stop;
            doubleAnimation.Completed += DoubleAnimation_Completed;
            this.BeginAnimation(OffsetProperty, doubleAnimation, HandoffBehavior.Compose);

        }
        //移动动画完成
        private void DoubleAnimation_Completed(object sender, EventArgs e)
        {
            this.anicount--;
            if (this.anicount == 0)
            {
                this.Offset = ((DoubleAnimation)((Clock)sender).Timeline).To.GetValueOrDefault();
                this.Regress();
            }
        }

        private int anicount = 0;


        /// <summary>
        /// 重置对象的位置
        /// </summary>
        private void ResetPosition()
        {
            var i = 0;
            foreach (var v3d in visual3D)
            {
                var index = i - CurrentIndex;
                var p = PositionProvider.GeneratePosition(index - this.Offset);
                var t3g = v3d.Transform as Transform3DGroup;
                var tt = t3g.Children[2] as TranslateTransform3D;
                tt.OffsetX = p.X;
                tt.OffsetY = p.Y;
                tt.OffsetZ = p.Z;
                i++;
            }
        }

        private Point lastMousePos;
        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            base.OnMouseDown(e);
            if (e.ChangedButton == MouseButton.Left)
            {
                lastMousePos = e.GetPosition(this);
                this.CaptureMouse();
            }


        }
        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            if (e.LeftButton == MouseButtonState.Pressed && this.IsMouseCaptured)
            {
                var pos = e.GetPosition(this);
                this.OnSlide(-(pos - lastMousePos).X / 100);
                lastMousePos = pos;
            }
        }
        protected override void OnMouseWheel(MouseWheelEventArgs e)
        {
            base.OnMouseWheel(e);
            OnSlide(-e.Delta / 120);
            Regress();
        }


        protected override void OnMouseUp(MouseButtonEventArgs e)
        {
            base.OnMouseUp(e);
            if (e.ChangedButton == MouseButton.Left && this.IsMouseCaptured)
            {
                this.ReleaseMouseCapture();
                this.Regress();
            }
        }
        protected void OnSlide(double offset)
        {
            if (offset == 0)
            {
                return;
            }
            this.Offset += (offset * this.SpeedRatio);

        }
        /// <summary>
        /// 滑动到相对于当前位置的偏移
        /// </summary>
        /// <param name="offset"></param>
        public void Slide(double offset)
        {
            this.OnSlide(offset);
        }
        /// <summary>
        /// 恢复到正位
        /// </summary> 
        public void Regress()
        {
            //if (Offset == 0)
            //{
            //    return;
            //}
            int offset = (int)Math.Round(Offset);//将偏移四舍五入
            int oldindex = this.CurrentIndex;//记录之前的索引
            var index = oldindex + offset;
            this.CurrentIndex = index;//根据偏移重置当前索引
            if (!AllowOutOfRange && this.CurrentIndex < 0)
            {
                this.Offset = this.Offset + (oldindex);
                this.CurrentIndex = 0;
                RegressAnimation();
            }
            else if (!AllowOutOfRange && this.CurrentIndex >= Children.Count)
            {
                this.Offset = this.Offset + (oldindex - (Children.Count - 1));
                this.CurrentIndex = Children.Count - 1;

                RegressAnimation();
            }
            else
            {
                if (offset == Offset)
                {
                    this.Offset = 0;
                }
                else
                {
                    this.Offset = Offset - offset;//偏移仅保留四舍五入后的补值
                    RegressAnimation();
                }
            }
        }
        /// <summary>
        /// 偏移回位动画，将四舍五入后残余的小数部分偏移置为0.
        /// </summary>
        private void RegressAnimation()
        {
            DoubleAnimation da = new DoubleAnimation() { DecelerationRatio = 0, Duration = TimeSpan.FromSeconds(0.5d), To = 0, FillBehavior = FillBehavior.Stop };
            //Storyboard.SetTarget(da, this);
            //Storyboard.SetTargetProperty(da, new PropertyPath(OffsetProperty.Name));
            da.Completed += Da_Completed;
            this.BeginAnimation(OffsetProperty, da, HandoffBehavior.Compose);
        }

        private void Da_Completed(object sender, EventArgs e)
        {
            this.Offset = 0;
        }
        #endregion

        #endregion

        #region 子控件
        private Viewport3D viewport3D;
        private Dictionary<Visual, Visual3D> visualMap = new Dictionary<Visual, Visual3D>();
        private List<Visual3D> visual3D = new List<Visual3D>();

        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public System.Collections.ObjectModel.ObservableCollection<UIElement> Children { get; private set; }
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            this.viewport3D = this.GetTemplateChild("VIEWPORT3D") as Viewport3D;
            InitVisual3D();
        }

        private void InitVisual3D()
        {
            if (viewport3D == null)
            {
                return;
            }
            visualMap.Clear();
            visual3D.Clear();
            foreach (var item in this.Children)
            {
                if (item is Visual v)
                {
                    addChild(v);
                }
            }
            this.ResetPosition();
        }

        private void Children_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (viewport3D == null)
            {
                return;
            }
            if (e.OldItems != null)
            {
                foreach (var item in e.OldItems)
                {
                    if (item is Visual vr)
                    {
                        removeChild(vr);
                    }
                }
            }
            if (e.NewItems != null)
            {
                foreach (var item in e.NewItems)
                {
                    if (item is Visual va)
                    {
                        addChild(va);
                    }
                }
            }
        }

        private void addChild(Visual va)
        {
            var v3d = CreateVisual3D(va);
            if (!visualMap.ContainsKey(va))
            {
                visualMap.Add(va, v3d);
            }
            if (!viewport3D.Children.Contains(v3d))
            {
                visual3D.Add(v3d);
                viewport3D.Children.Add(v3d);
            }
            ResetPosition();
        }

        private void removeChild(Visual vr)
        {
            if (visualMap.ContainsKey(vr))
            {
                var v3d = visualMap[vr];
                visualMap.Remove(vr);
                if (viewport3D.Children.Contains(v3d))
                {
                    visual3D.Remove(v3d);
                    viewport3D.Children.Remove(v3d);
                }

            }
            ResetPosition();

        }

        private Visual3D CreateVisual3D(Visual v)
        {
            Viewport2DVisual3D v2d = new Viewport2DVisual3D();
            //-1,1,0 -1,-1,0 1,-1,0 1,1,0
            //0,0 0,1 1,1 1,0
            v2d.Geometry = new MeshGeometry3D()
            {
                Positions = new Point3DCollection() { new Point3D(-1, 1, 0), new Point3D(-1, -1, 0), new Point3D(1, -1, 0), new Point3D(1, 1, 0) },
                TextureCoordinates = new PointCollection() { new Point(0, 0), new Point(0, 1), new Point(1, 1), new Point(1, 0) },
                TriangleIndices = new Int32Collection() { 0, 1, 2, 0, 2, 3 }
            };
            v2d.Material = new DiffuseMaterial() { Brush = Brushes.White };
            Viewport2DVisual3D.SetIsVisualHostMaterial(v2d.Material, true);
            var tg = new Transform3DGroup();
            tg.Children.Add(new RotateTransform3D());
            tg.Children.Add(new ScaleTransform3D());
            tg.Children.Add(new TranslateTransform3D());
            v2d.Transform = tg;
            v2d.Visual = v;
            return v2d;
        }

        void IAddChild.AddChild(object value)
        {
            if (value == null)
            {
                throw new ArgumentNullException("value");
            }

            UIElement uie = value as UIElement;

            if (uie == null)
            {
                throw new ArgumentException();
            }

            Children.Add(uie);
        }

        void IAddChild.AddText(string text)
        {
            // throw new NotImplementedException();
        }


        #endregion
    }

    #region 位置提供器

    /// <summary>
    /// 为3D相册提供位置信息
    /// </summary>
    public interface IGallery3DPositionProvider
    {

        /// <summary>
        /// 为指定的索引和偏移提供位置
        /// </summary> 
        /// <param name="offset">位置的百分比偏移，-1表示向前一个索引位置偏移,-0.5表示偏移到前一个索引位置的中间点。1表示像后一个索引位置偏移。</param>
        /// <returns></returns>
        Point3D GeneratePosition(double offset);
    }
    /// <summary>
    /// 内容均匀分布在一个指定顶点角上
    /// </summary>
    public class CornerGallery3DPositionProvider : IGallery3DPositionProvider
    {
        /// <summary>
        /// 顶点位置
        /// </summary>
        public Point3D OriginalPosition { get; set; } = new Point3D(0, 0, 0);
        /// <summary>
        /// 边1方向，负数索引所在的边(该向量的长度决定了两个元素间的间隔)
        /// </summary>
        public Vector3D Side1 { get; set; } = new Vector3D(-4, 0, -2);
        /// <summary>
        /// 边2方向，正数索引所在的边(该向量的长度决定了两个元素间的间隔)
        /// </summary>
        public Vector3D Side2 { get; set; } = new Vector3D(4, 0, -2);
        /// <summary>
        /// 夹角（度）
        /// </summary>
        public double Angle { get; set; } = 120;

        public Point3D GeneratePosition(double offset)
        {
            if (offset == 0)
            {
                return this.OriginalPosition;
            }
            if (offset < 0)
            {
                return OriginalPosition + Side1 * Math.Abs(offset);
            }
            else
            {//>0
                return OriginalPosition + Side2 * Math.Abs(offset);
            }
        }
    }

    /// <summary>
    /// 在圆弧上布局
    /// </summary>
    public class ArcGallery3DPositionProvider : IGallery3DPositionProvider
    {
        public ArcGallery3DPositionProvider()
        {
            propChange();
        }
        private Point3D _Center = new Point3D(0, 0, -3);

        /// <summary>
        /// 圆心位置
        /// </summary> 
        public Point3D Center
        {
            get { return _Center; }
            set
            {
                _Center = value;
                propChange();
            }
        }


        private Point3D _OriginalPosition = new Point3D();

        /// <summary>
        /// 索引为0的对象所在的位置
        /// </summary> 
        public Point3D OriginalPosition
        {
            get { return _OriginalPosition; }
            set
            {
                _OriginalPosition = value;
                propChange();
            }
        }
        /// <summary>
        /// 图块中心点的间距(单个图块为2个单位长度的矩形)
        /// </summary>
        public double Gap { get; set; } = 1;

        private void propChange()
        {
            originalVector = OriginalPosition - Center;
            var zero_xV = new Vector3D(0, originalVector.Y, originalVector.Z);//使原始向量保持在Y-Z面上（以保证圆面始终平行于X轴）
            RotateTransform3D rotateTransform3D = new RotateTransform3D();
            rotateTransform3D.Rotation = new AxisAngleRotation3D(new Vector3D(1, 0, 0), -90);
            axis = rotateTransform3D.Transform(zero_xV);//Y-Z面的向量旋转90度得到圆心轴

        }

        private Vector3D originalVector;//原始位置的向量
        private Vector3D axis;//圆面的圆心轴
        public Point3D GeneratePosition(double offset)
        {
            if (offset == 0)
            {
                return OriginalPosition;
            }
            var radius = originalVector.Length;
            //周长公式2*PI*R，即每一单位的弧长对应的弧度为1/R
            var rad = (offset * Gap) / radius;//偏转的弧度
            RotateTransform3D rotateTransform3D = new RotateTransform3D();
            rotateTransform3D.Rotation = new AxisAngleRotation3D(axis, rad * (180d / Math.PI));
            var v = rotateTransform3D.Transform(originalVector);//旋转得到新的向量
            return Center + v;
        }
    }
    /// <summary>
    /// 在椭圆上布局
    /// </summary>
    /// <remarks>
    /// <see cref="Center"/>到<see cref="OriginalPosition"/>的向量（V）长度代表椭圆一边宽的1/2，<see cref="Radius"/>代表与该向量垂直的另一边的宽，当V*2=<see cref="Radius"/>时，该椭圆为正圆。
    /// </remarks>
    public class EllipseGallery3DPositionProvider : IGallery3DPositionProvider
    {

        private Point3D _Center = new Point3D(0, 0, -10);

        /// <summary>
        /// 椭圆的中心位置
        /// </summary> 
        public Point3D Center
        {
            get { return _Center; }
            set
            {
                _Center = value;
                propChange();
            }
        }


        private Point3D _OriginalPosition = new Point3D();

        /// <summary>
        /// 索引为0的对象所在的位置
        /// </summary> 
        public Point3D OriginalPosition
        {
            get { return _OriginalPosition; }
            set
            {
                _OriginalPosition = value;
                propChange();
            }
        }

        private double _Radius = 5d;
        /// <summary>
        /// 椭圆另一边的半径
        /// </summary>
        public double Radius
        {
            get { return _Radius; }
            set
            {
                _Radius = value;
                propChange();
            }
        }

        /// <summary>
        /// 图块中心点的间距(单个图块为2个单位长度的矩形)
        /// </summary>
        public double Gap { get; set; } = 1;

        private Vector3D originalVector;//原始位置的向量
        private Vector3D axis;//椭圆面的中心轴
        private void propChange()
        {
            originalVector = OriginalPosition - Center;
            if (Radius <= 0)
            {
                throw new ArgumentException("Width不允许小于或等于0.");
            }
            if (originalVector.Length == 0)
            {
                throw new ArgumentException("Center和OriginalPosition不能是同一个点。");
            }
            var zero_xV = new Vector3D(0, originalVector.Y, originalVector.Z);//使原始向量保持在Y-Z面上（以保证椭圆面始终平行于X轴）
            RotateTransform3D rotateTransform3D = new RotateTransform3D();
            rotateTransform3D.Rotation = new AxisAngleRotation3D(new Vector3D(1, 0, 0), -90);
            axis = rotateTransform3D.Transform(zero_xV);//Y-Z面的向量旋转90度得到圆心轴

        }

        public Point3D GeneratePosition(double offset)
        {
            if (offset == 0)
            {
                return OriginalPosition;
            }
            var radius = originalVector.Length;//圆形半径。
            //圆周长公式2*PI*R，即每一单位的弧长对应的弧度为1/R
            var rad = (offset * Gap) / radius;//偏转的弧度

            Transform3DGroup t3g = new Transform3DGroup();
            RotateTransform3D rotateTransform3D = new RotateTransform3D();
            rotateTransform3D.Rotation = new AxisAngleRotation3D(axis, rad * (180d / Math.PI));//首先旋转到对应方向
            t3g.Children.Add(rotateTransform3D);
            ScaleTransform3D scaleTransform3D = new ScaleTransform3D();


            var width_V = rotateVector(originalVector, 90);//将原始向量旋转90度得到圆形的另一条垂直的直径向量
            var ell_V = width_V * (Radius / width_V.Length);//缩放成椭圆的另一条轴线
            scaleTransform3D.ScaleX = ell_V.X / width_V.X;
            scaleTransform3D.ScaleY = ell_V.Y / width_V.Y;
            scaleTransform3D.ScaleZ = ell_V.Z / width_V.Z;//计算缩放
            if (double.IsNaN(scaleTransform3D.ScaleX))
            {
                scaleTransform3D.ScaleX = 1;
            }
            if (double.IsNaN(scaleTransform3D.ScaleY))
            {
                scaleTransform3D.ScaleY = 1;
            }
            if (double.IsNaN(scaleTransform3D.ScaleZ))
            {
                scaleTransform3D.ScaleZ = 1;
            }
            t3g.Children.Add(scaleTransform3D);
            var newV = t3g.Transform(originalVector);
            return Center + newV;
        }
        /// <summary>
        /// 按圆心轴旋转向量
        /// </summary>
        /// <param name="v"></param>
        /// <param name="angle"></param>
        /// <returns></returns>
        private Vector3D rotateVector(Vector3D v, double angle)
        {
            RotateTransform3D rotateTransform3D = new RotateTransform3D();
            rotateTransform3D.Rotation = new AxisAngleRotation3D(axis, angle);
            var vr = rotateTransform3D.Transform(v);
            vr.X = Math.Round(vr.X, 8);
            vr.Y = Math.Round(vr.Y, 8);
            vr.Z = Math.Round(vr.Z, 8);
            return vr;
        }


    }
    #endregion
}
